/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ru.niir.goodfil.ui;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Font;
import ru.niir.goodfil.audit.domain.OrderItemUIEvent;
import ru.niir.goodfil.audit.domain.OrderUIEvent;
import ru.niir.goodfil.audit.domain.enums.OrderItemUIEventType;
import ru.niir.goodfil.audit.domain.enums.OrderUIEventType;
import ru.niir.goodfil.db.*;
import ru.niir.goodfil.db.dic.ClientState;
import ru.niir.goodfil.db.dic.DeliveryType;
import ru.niir.goodfil.db.dic.OrderState;
import ru.niir.goodfil.db.dic.StorageDeliveryState;
import ru.niir.goodfil.exceptions.*;
import ru.niir.goodfil.ui.utils.FacesUtils;
import ru.niir.goodfil.ui.utils.RestsExportManager;
import ru.niir.goodfil.utils.*;
import ru.niir.goodfil.utils.filters.OrdersFilter;
import ru.niir.goodfil.utils.validation.ValidationError;
import ru.niir.goodfil.utils.validation.ValidationResult;

import javax.faces.model.SelectItem;
import javax.mail.MessagingException;
import javax.transaction.Transaction;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.*;

import static ru.niir.goodfil.utils.Utils._assertNotNull;

/**
 * @author Носов А.В.
 */
public class OrderBean extends PageBean {
    private static Logger logger = Logger.getLogger(OrderBean.class);

    /**
     * количество изменяемого товара в заказе
     */
    private Map<Long, Integer> orderItemsCount;

    /**
     * Заказ
     */
    private Order order;

    /**
     * Товар в заказе
     */
    private OrderItem orderItem = new OrderItem();

    /**
     * Список товаров в заказе
     */
    private List<OrderItem> orderItems;

    /**
     * Цена, установленная вручную менеджером
     */
    private float manualPrice;

    /**
     * Идентификатор OrderItem
     */
    private long orderItemId;

    /**
     * Поле используется для хранения даты, введенной пользователем при отправке
     * предоплатного заказа на выдачу.
     */
    private Date awaitingDeliveryDate;

    /**
     * Флаг, указывающий, есть ли у данного заказа, заказы, доступные для объединения.
     */
    private boolean haveOrdersForCombine;

    /**
     * Id заказа, выбранного для объединения
     */
    private long orderForCombineId;

    /**
     * Конструктор
     */
    public OrderBean() {
        init();
    }

    public void init(){
        order = getOrder();
        List<Order> listOfOrdersForCombine = getOrdersForCombine();
        if (listOfOrdersForCombine.size()==0) haveOrdersForCombine = false;
        else haveOrdersForCombine = true;
    }

    public boolean isHaveOrdersForCombine() {
        return haveOrdersForCombine;
    }

    public void setHaveOrdersForCombine(boolean haveOrdersForCombine) {
        this.haveOrdersForCombine = haveOrdersForCombine;
    }



    public long getOrderForCombineId() {
        return orderForCombineId;
    }

    public void setOrderForCombineId(long orderForCombineId) {
        this.orderForCombineId = orderForCombineId;
    }

    /**
     * Возвращает количество изменяемого товара в заказе.
     *
     * @return карта количества товара в заказе
     */
    public Map<Long, Integer> getOrderItemsCount() {
        return orderItemsCount;
    }

    /**
     * Устанавливает количество изменяемого товара в заказе.
     *
     * @param orderItemsCount карта количества товара в заказе
     */
    public void setOrderItemsCount(Map<Long, Integer> orderItemsCount) {
        this.orderItemsCount = orderItemsCount;
    }

    /**
     * Возвращает выбранный заказ.
     *
     * @return заказ
     */
    public Order getOrder() {
        if (order == null) {
            if (getSessionBean().getSelectedOrderId() == -1) return null;
            order = gf().getOrderById(getSessionBean().getSelectedOrderId());

            Client client = gf().getClientById(order.getClientId());
            order.setClient(client);
            order.setManager(gf().getUserById(order.getManagerId()));
            if(order.getOrderStateIsOutput() || order.getOrderStateIsComplete()){
                order.setStorekeeper(gf().getUserById(order.getStorekeeperId()));
            }
            getSessionBean().setOrder(order);
        }
        return order;
    }

    /**
     * Возвращает товар в заказе.
     *
     * @return товар в заказе
     */
    public OrderItem getOrderItem() {
        return orderItem;
    }

    /**
     * Устанавливает товар в заказе.
     *
     * @param orderItem товар в заказе
     */
    public void setOrderItem(OrderItem orderItem) {
        this.orderItem = orderItem;
    }

    /**
     * Возвращает список товаров в заказе.
     *
     * @return список товаров
     */
    public List<OrderItem> getOrderItemList() {
        if (orderItemsCount == null) {
            orderItems = gf().getOrderItemsByOrderId(getSessionBean().getSelectedOrderId());
            orderItemsCount = new HashMap<Long, Integer>();

            for (OrderItem oi : orderItems) {
                oi.setGood(gf().getGoodById(oi.getGoodId()));
                orderItemsCount.put(oi.getId(), oi.getCount());
            }
        }

        Collections.sort(orderItems, new Comparator<OrderItem>() {
            public int compare(OrderItem o1, OrderItem o2) {
                return o1.getGood().getCode().compareTo(o2.getGood().getCode());
            }
        });
        return orderItems;

    }

    public BigDecimal getOrderWeight() {
        List<OrderItem> orderItems = getOrderItemList();
        if (orderItems == null) return new BigDecimal(0).setScale(5, RoundingMode.HALF_UP);

        float result = 0;
        for (OrderItem orderItem : orderItems) {
            result += (orderItem.getGood().getWeight() * orderItem.getCount());
        }


        return new BigDecimal(String.valueOf(result)).setScale(5, RoundingMode.HALF_UP);
    }

    public BigDecimal getOrderVolume() {
        List<OrderItem> orderItems = getOrderItemList();
        if (orderItems == null) return new BigDecimal(0).setScale(5, RoundingMode.HALF_UP);

        float result = 0;
        for (OrderItem orderItem : orderItems) {
            result += (orderItem.getGood().getVolume() * orderItem.getCount());
        }
        return new BigDecimal(String.valueOf(result)).setScale(5, RoundingMode.HALF_UP);
    }

    /**
     * Вовзращает список типов доставки.
     *
     * @return список типов доставки
     */
    public List<SelectItem> getDeliveryTypesAsSelectItems() {
        List<SelectItem> list = new ArrayList<SelectItem>();
        List<Pair> pairs = DeliveryType.list();
        for (Pair pair : pairs) list.add(new SelectItem(pair.getId(), pair.getValue()));
        return list;
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Работа с БД----------
     * -------------------------------------------------------------------------
     */

    /**
     * Удаление товара из заказа.
     */
    public void deleteOrderItem() {
        Order checkOrder = gf().getOrderById(order.getId());
        if(!checkOrder.getOrderIsEditableWithReserves()){
            facesInfo("Данный заказ не подлежит изменению. Невозможно удалить позицию");
            return;
        }
        try {
            Order o = getOrder();
            gf().addOrderInfo(o);

            Long orderItemId = orderItem.getId();
            OrderItem orderItem = gf().getOrderItemById(orderItemId);

            OrderItemUIEvent event = prepareOrderItemUIEvent(OrderItemUIEventType.REMOVED, o, orderItem);

            // Если включены резервы в ИС и статус не предварительно сохранен,
            // то обновляем количество зарезервированно товара
            if(gf().getIsReservedOn()){
                if(!getOrderStateIsPreSaved()){
                    Good good = gf().getGoodById(orderItem.getGoodId());
                    good.setReservedCount(good.getReservedCount() - orderItem.getCount());
                    try {
                        gf().updateGood(good);
                    } catch (ValidationException e) {
                        logger.error(e.getMessage());
                    }
                }
            }
            gf().removeOrderItem(orderItemId);
            orderItemsCount = null;
            facesInfo("Товар удален из заказа");

//            getAuditService().logOrderItemUIEvent(event);
        } catch (RemoveFailureException ex) {
            logger.error(ex);
            ex.printStackTrace();
            String[] objs = ex.getRemoveCheckResult().getLinkedObjects();
//            for (String obj : objs) {
            facesError("Товар нельзя удалить, т.к. он связан с другими объектами.");
//            }
        } catch (InvalidIdException ex) {
            logger.error(ex);
            ex.printStackTrace();
            facesError("Указанная позиция заказа отсутствует");
        }
    }

    /**
     * Удаление из корзины товаров.
     */
    public void updateOrderItem() {
        Order checkOrder = gf().getOrderById(order.getId());
        if(!checkOrder.getOrderIsEditableWithReserves()){
            facesInfo("Данный заказ не подлежит изменению. Невозможно изменить количество в заказе");
            return;
        }
        try {
            int goodsCount = Integer.valueOf(String.valueOf(orderItemsCount.get(orderItem.getId())));
            if (hasFacesErrors()) return;
            OrderItem oi = gf().getOrderItemById(orderItem.getId());
            // Если включены резервы в ИС и статус не предварительно сохранен,
            // то обновляем количество зарезервированно товара
            if(gf().getIsReservedOn()){
                if(!gf().getOrderById(getSessionBean().getSelectedOrderId()).getOrderStateIsPreSaved()){
                    Good good = gf().getGoodById(oi.getGoodId());
                    int difference = goodsCount - oi.getCount();
                    if((good.getCount() - good.getReservedCount()) < difference){
                        facesError("На складе надостаточно товара " + good.getCode() + ". Изменение невозможно.");
                        return;
                    }
                    good.setReservedCount(good.getReservedCount() + difference);
                    gf().updateGood(good);
                }
            }
            oi.setCount(goodsCount);

            logger.debug("Updating goods count for order: " + orderItem.getOrderId() + ", order item: " + orderItem.getId() + ": " + goodsCount);

            gf().cleverUpdateOrderItem(oi);
            orderItemsCount = null;

            if (goodsCount == 0) {
                facesInfo("Товар удален из заказа");
                Order o = getOrder();
                gf().addOrderInfo(o);
                getAuditService().logOrderItemUIEvent(prepareOrderItemUIEvent(OrderItemUIEventType.REMOVED, o, orderItem));
            } else {
                facesInfo("Количество товара изменено (" + goodsCount + " шт.)");
                Order o = getOrder();
                gf().addOrderInfo(o);
//                getAuditService().logOrderItemUIEvent(prepareOrderItemUIEvent(OrderItemUIEventType.COUNT_CHANGED, o, orderItem));
            }
        } catch (Exception e) {
            logger.error(e);
            e.printStackTrace();

            facesError("Не удалось изменить кол-во товара в заказе");
        }
    }

    /**
     * Установить цену на товар вручную
     */
    public void applyManualPrice() {
        if (hasFacesErrors()) return;

        try {
            orderItemId = Long.parseLong(getSessionMap().get("orderItemId").toString());

            logger.debug("OrderItemId:  " + orderItemId);
            logger.debug("Manual price: " + manualPrice);


            OrderItem oi = gf().getOrderItemById(orderItemId);
            oi.setManualPrice(manualPrice);

            gf().updateOrderItem(oi);
            facesInfo("Позиция заказа обновлена");
            orderItemsCount = null;

            Order o = getOrder();
            gf().addOrderInfo(o);
            getAuditService().logOrderItemUIEvent(prepareOrderItemUIEvent(OrderItemUIEventType.REMOVED, o, oi));

            facesInfo("Цена на позицию заказа была установлена вручную");
        } catch (ValidationException ex) {
            logger.error(ex);
            ex.printStackTrace();
            ValidationResult validationResult = ex.getResult();
            for (ValidationError error : validationResult.getErrors()) {
                facesError(error.toHumanString());
            }
        }
    }

    /**
     * Сбросить цену, установленную на позицию заказа вручную
     */
    public void resetManualPrice() {
        if (hasFacesErrors()) return;

        try {
            orderItemId = Long.parseLong(getSessionMap().get("orderItemId").toString());

            logger.debug("OrderItemId:  " + orderItemId);

            OrderItem oi = gf().getOrderItemById(orderItemId);
            oi.setManualPrice(0);

            gf().updateOrderItem(oi);
            facesInfo("Позиция заказа обновлена");
            orderItemsCount = null;

            Order o = getOrder();
            gf().addOrderInfo(o);
            getAuditService().logOrderItemUIEvent(prepareOrderItemUIEvent(OrderItemUIEventType.REMOVED, o, oi));

            facesInfo("Цена на позицию заказа, установленная вручную была отменена");
        } catch (ValidationException ex) {
            logger.error(ex);
            ex.printStackTrace();
            ValidationResult validationResult = ex.getResult();
            for (ValidationError error : validationResult.getErrors()) {
                facesError(error.toHumanString());
            }
        }
    }

    /**
     * Функция проверяет, что в заказ входят только товары, для которых установлен код соответствия 1С
     *
     * @return false, если в заказе присутствует товар, для которого не установлен код соответствия 1С
     */
    private boolean checkOrder1C() {
        Order order = getOrder();
        List<OrderItem> orderItems = gf().getOrderItemsByOrderId(order.getId());
        for (OrderItem orderItem : orderItems) {
            Good good = gf().getGoodById(orderItem.getGoodId());
            _assertNotNull(good, "В заказе присутствует несуществующий товар");

            if (good.getC1CodeIsMissing()) {
                facesError("Для товара " + good.getCode() + " " + good.getName() + " отсутствует код соответствия с 1С");
                return false;
            }
        }
        return true;
    }

    /**
     * Согласовать заказ
     */
    public void validateOrder() throws IOException, ParseException {

        if(order.getDeliveryTypeId() == DeliveryType.TRANSPORT_COMPANY_RETURN){
            try {
                gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.DELIVERY);
                Calendar now = Calendar.getInstance();
                now.set(Calendar.HOUR_OF_DAY, 0);
                now.set(Calendar.MINUTE, 0);
                now.set(Calendar.SECOND, 0);
                now.set(Calendar.MILLISECOND, 0);
                if (now.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY) {
                    now.add(Calendar.DATE, 3);
                } else if (now.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
                    now.add(Calendar.DATE, 2);
                } else if (order.getClient().getClientStateId() != ClientState.E_SHOP_DAY_TO_DAY) {
                    now.add(Calendar.DATE, 1);
                }
               gf().saveStorageDeliveryStatus(new StorageDeliveryStatus(order.getId(), order.getDeliveryDateIsInPast() ? now.getTime() : order.getDeliveryDate(), order.getDeliveryTypeId(), order.getClient().getClientStateId()));
                facesInfo("Добавлено в лист развоза");
            } catch (EmptyOrderException e) {
                e.printStackTrace();
            } catch (OrderStateChangeException e) {
                facesError(e.toString());
            }
        }
        else {
            try {
                //  Проверяем, что выбрана фирма
                Order _order = gf().getOrderById(getSessionBean().getSelectedOrderId());
                if (_order.getFirm() < 1) {
                    facesError("Не выбрана фирма, через которую проводится заказ. НЕ ЗАБУДЬТЕ СОХРАНИТЬ!");
                    return;
                }

                //  Проверяем, что в заказ входят только товары, для которых утсановлен код соответствия 1С
                if (!checkOrder1C()) {
                    facesError("В заказ входят товары, для которых НЕ установлен код соответствия с 1С. Свяжитесь с Администратором.");
                    return;
                }
                {
                    Client client = gf().getClientById(order.getClientId());
                    if(client.getClientIsBlock()){
                        facesError("Клиент заблокирован. Невозможно отправить заказ на склад");
                        return;
                    }
                }

                if(gf().getIsReservedOn()){
                    if(_order.getDeliveryDate() == null){
                        facesError("Вы не указали дату отгрузки");
                        return;
                    }
                }

                //  Проверяем, что 1С запущена
                if(!gf().getIsReservedOn()){
                    if (!Logs1CBean.get1CIsRunning(gf())) {
                        facesError("НЕЛЬЗЯ СОГЛАСОВАТЬ ЗАКАЗ, т.к. ОБМЕН С 1С НЕ ЗАПУЩЕН. СВЯЖИТЕСЬ С АДМИНИСТРАТОРОМ.");
                        return;
                    }
                }

                Logger theLogger = Logger.getLogger("OrderValidatedInfo");
                StringBuilder sb = new StringBuilder();
                sb.append("Order " + getSessionBean().getSelectedOrderId() + " saved ");
                sb.append("(" + order.getId() + ") ");
                sb.append("client: " + Transliter.translit(order.getClient().getCompanyName()) + ", ");
                sb.append("sum: " + getSessionBean().getTotal() + ", ");
                sb.append("creationDate: " + order.getCreationDateAsString());
                theLogger.debug(sb.toString());

                gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.AWAITING_VALIDATION);
                if(gf().getIsReservedOn()){
                    gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.OUTPUT);
                }
                else {
                    gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.VALIDATED);
                }
                gf().addOrderInfo(order);
//            getAuditService().logOrderUIEvent(prepareOrderUIEvent(OrderUIEventType.VALIDATED, order));

                // Если установлен соответствующий флаг - отправить сб. накладную.
                if(order.getClient().isSendInvoice()){
                    Calculation calculation = new Calculation();
                    calculation.makeCalculation(getOrderItemList(), gf().getOrderById(getSessionBean().getSelectedOrderId()));
                    try {
                        sendClientNotification(gf().getOrderById(getSessionBean().getSelectedOrderId()).getManagerId(), gf().getOrderById(getSessionBean().getSelectedOrderId()).getId());
                    } catch (MailerException e) {
                        logger.error("Mailer Exception: " + e.getMessage());
                    }
                }
                order = null;
                facesInfo("Заказ отправлен на склад");



            } catch (EmptyOrderException eo) {
                facesError("Выполнение операций над пустым заказом не допускается");
            } catch (OrderStateChangeException e) {
                facesError(e.toString());
            }
        }


    }

    private void sendClientNotification(long userId, long orderId) throws MailerException {
        RestsExportManager restsExportManager = new RestsExportManager();
        try {
            if(!gf().getUserById(userId).getEmail().equals("ee@"))
            restsExportManager.sendCalculation(gf(), userId, orderId);
        } catch (IOException e) {
            logger.error("I/O Exception: " + e.getMessage());
        } catch (MessagingException e) {
            logger.error("MessagingException: " + e.getMessage());

        }
    }

    /**
     * Не согласовать заказ
     */
    public void notValidateOrder() {
        try {
            //  EXPERIMENTAL: Проверяем, что в заказ входят только товары, для которых утсановлен код соответствия 1С
            if (!checkOrder1C()) return;

            logger.debug("Not validating: " + getSessionBean().getSelectedOrderId());

            gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.AWAITING_VALIDATION);
            gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.NOT_VALIDATED);
            order = null;

            facesInfo("Заказ переведен в статус 'Не согласован'");
        } catch (EmptyOrderException eo) {
            facesError("Выполнение операций над пустым заказом не допускается");
        } catch (OrderStateChangeException e) {
            facesError(e.toString());
        }
    }

    /**
     * Оформить заказ
     * <p/>
     * EXPERIMENTAL: Когда клиент нажимает "Оформить заказ" начинает работать механизм резервирования. Механизм
     * резервирования состоит из двух частей: 1. В заказе устанавливается дата резервирования, как текущая дата +
     * интвервал в днях, заданный системной настройкой orders.reservation.timeout. Если по истечении этого срока, заказ
     * не переходит в статус "оплачен", каждый день менеджеру высылается уведомление про просроченый заказ. 2.
     * Резервируется необходимое кол-во товара
     */
    public synchronized void doSaveOrder() {

        try {
            //  Проверяем, что в заказ входят только товары, для которых утсановлен код соответствия 1С
            if (!checkOrder1C()) return;

            //  Проверяем, что выбрана фирма
            {
                if(!gf().getIsReservedOn()){
                    Order _order = gf().getOrderById(getSessionBean().getSelectedOrderId());
                    if (_order.getFirm() < 1) {
                        facesError("Не выбрана фирма, через которую проводится заказ. НЕ ЗАБУДЬТЕ СОХРАНИТЬ!");
                        return;
                    }
                }
            }

            //  Провермяем, что указана дата отгрузки.
            //  Если не указана - выдаем предупреждающее сообщение, но продолжаем работать
            {
                Order _order = gf().getOrderById(getSessionBean().getSelectedOrderId());
                if (_order.getDeliveryDate() == null) {
                    if(!gf().getIsReservedOn()){
                        facesInfo("ВНИМАНИЕ! Вы не указали дату отгрузки! Если дата отгрузки не указана, заказ не попадет на склад.");
                    }
                    // Если включены резервы на стороне ИС, то нельзя на склад отправлять заказы без даты отгрузки
                    else if(gf().getIsReservedOn()){
                        facesError("ВНИМАНИЕ! Вы не указали дату отгрузки!");
                        return;
                    }
                }
            }

            // Установка для заказа даты резервирования
            Calendar c = Calendar.getInstance();
            c.add(Calendar.DATE, gf().getInt("orders.reservation.timeout"));
            order.setReservationDate(new Timestamp(c.getTimeInMillis()));
            gf().updateOrder(order);

            // Формирование реального резерва
            List<OrderItem> orderItems = gf().getOrderItemsByOrderId(getSessionBean().getSelectedOrderId());
            for (OrderItem orderItem : orderItems) {
                Good good = gf().getGoodById(orderItem.getGoodId());
                good.setReservedCount(good.getReservedCount() + orderItem.getCount());
                if (good.getReservedCount() > good.getCount()) {
                    good.setReservedCount(good.getReservedCount() - orderItem.getCount());
                    facesError("Недостаточное кол-во товара \"" + good.getCode() + " " + good.getName() + "\". В наличии всего: " + (good.getCount() - good.getReservedCount()));
                    gf().saveGoodAbsence(
                            new GoodAbsence(order.getManagerId(),
                                           orderItem.getGoodId(),
                                           order.getClientId(),
                                           orderItem.getCount() - (good.getCount() - good.getReservedCount()),
                                           orderItem.getItemPrice())
                                           );
                    }
                    gf().updateGood(good);
            }

            if (hasFacesErrors()) return;

            Logger theLogger = Logger.getLogger("OrderSavedInfo");
            StringBuilder sb = new StringBuilder();
            sb.append("Order " + getSessionBean().getSelectedOrderId() + " saved ");
            sb.append("(" + order.getId() + ") ");
            sb.append("client: " + Transliter.translit(order.getClient().getCompanyName()) + ", ");
            sb.append("sum: " + getSessionBean().getTotal() + ", ");
            sb.append("creationDate: " + order.getCreationDateAsString());
            theLogger.debug(sb.toString());

            gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.SAVED);
            gf().setOrderState(getSessionBean().getSelectedOrderId(), OrderState.AWAITING_VALIDATION);

            gf().addOrderInfo(order);
 //           getAuditService().logOrderUIEvent(prepareOrderUIEvent(OrderUIEventType.SAVED, order));

            this.order = null;
            facesInfo("Заказ переведен в статус 'Оформлен'");
            facesInfo("Товар зарезервирован");
        } catch (EmptyOrderException eo) {
            facesError("Выполнение операций над пустым заказом не допускается");
        } catch (OrderStateChangeException e) {
            facesError(e.getMessage());
        } catch (ValidationException e) {
            facesError("Не удалось произвести резервирование товара");
        }
    }

    private OrderItemUIEvent prepareOrderItemUIEvent(OrderItemUIEventType type, Order order, OrderItem orderItem) {
        OrderItemUIEvent event = new OrderItemUIEvent();

        event.setIp(getAuditHelper().getIp());
        event.setLogin(getAuditHelper().getLogin());
        event.setType(type);

        event.setOrderId(String.valueOf(order.getId()));
        event.setC1Num(order.getC1Num());

        Client client = order.getClient();
        if (client == null) client = gf().getClientById(order.getClientId());
        event.setClientId(String.valueOf(client.getId()));
        event.setClientName(client.getCompanyName());

        User manager = order.getManager();
        if (manager == null) manager = gf().getUserById(order.getManagerId());
        event.setManagerId(String.valueOf(manager.getId()));
        event.setManagerName(manager.getFio());

        event.setCreationDate(order.getCreationDateAsString());
        event.setDeliveryDate(order.getDeliveryDateAsString());
        event.setOrderComment(getOrder1CString());
        event.setOrderSum(String.valueOf(order.getSum()));

        Good good = orderItem.getGood();
        if (good == null) good = gf().getGoodById(orderItem.getGoodId());

        event.setGoodId(String.valueOf(good.getId()));
        event.setGoodCode(good.getCode());

        event.setCount(String.valueOf(orderItem.getCount()));
        event.setPrice(String.valueOf(orderItem.getItemPrice()));

        return event;
    }

    private OrderUIEvent prepareOrderUIEvent(OrderUIEventType type, Order order) {
        OrderUIEvent event = new OrderUIEvent();

        event.setIp(getAuditHelper().getIp());
        event.setLogin(getAuditHelper().getLogin());
        event.setType(type);

        event.setOrderId(String.valueOf(order.getId()));
        event.setC1Num(order.getC1Num());

        Client client = order.getClient();
        if (client == null) client = gf().getClientById(order.getClientId());
        event.setClientId(String.valueOf(client.getId()));
        event.setClientName(client.getCompanyName());

        User manager = order.getManager();
        if (manager == null) manager = gf().getUserById(order.getManagerId());
        event.setManagerId(String.valueOf(manager.getId()));
        event.setManagerName(manager.getFio());

        event.setCreationDate(order.getCreationDateAsString());
        event.setDeliveryDate(order.getDeliveryDateAsString());
        event.setOrderComment(getOrder1CString());
        event.setOrderSum(String.valueOf(order.getSum()));

        return event;
    }

    public String getOrder1CString() {
        return new ExportStringFormatter(getOrder(), getOrderItemList()).formatExportString();
    }

    /**
     * Поле используется для хранения даты, введенной пользователем при отправке
     * предоплатного заказа на выдачу.
     */
    public void setAwaitingDeliveryDate(Date awaitingDeliveryDate) {
        this.awaitingDeliveryDate = awaitingDeliveryDate;
    }

    /**
     * Поле используется для хранения даты, введенной пользователем при отправке
     * предоплатного заказа на выдачу.
     */
    public Date getAwaitingDeliveryDate() {
        return awaitingDeliveryDate;
    }

    /**
     * Менеджер отдает команду на выдачу предоплатного заказа.
     *
     * @throws ValidationException
     */
    public void awaitingDeliveryAction() throws ValidationException {

        if(gf().getIsReservedOn() && awaitingDeliveryDate == null){
            facesError("ВНИМАНИЕ! Вы не указали дату отгрузки");
            return;
        }

        // 1. Если резерв фиксируем дату изменения

        if(order.getDeliveryDate() == null){

            order.setChangeDate(new Date());
            /*for(OrderItem item : orderItems){
                item.setGood(gf().getGoodById(item.getGoodId()));
                PriceInfo pi = gf().getPrice2(item.getGoodId(),
                        item.getGood().getBrandId(),
                        order.getClientId(),
                        getSessionBean().getSelectedClientGroupId2(item.getGood().getBrandId()));

                item.setStartPrice(pi.getPrice());
                item.setPersonalPrice(pi.isPersonalPrice());
            }
            gf().updateOrderItems(orderItems);*/
        }
        order.setDeliveryDate(awaitingDeliveryDate);
        gf().updateOrder(order);
        try {
            RestsExportManager.sendOrderChangeMessageForStoreKeeper(gf(),order.getId());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MessagingException e) {
            e.printStackTrace();
        }

         /*
          2. Обновляем дату в элемента листа развоза.
         */
        if(awaitingDeliveryDate != null && order.getOrderStateIsOnDelivery()){
            List<StorageDeliveryStatus> deliveryStatusList = gf().getStorageDeliveryStatusByOrderId(order.getId());
                if(deliveryStatusList.size() == 0){
                    gf().saveStorageDeliveryStatus(new StorageDeliveryStatus(order.getId(), awaitingDeliveryDate, order.getDeliveryTypeId(), order.getClient().getClientStateId()));
                }
                else {
                    for(StorageDeliveryStatus status : deliveryStatusList){
                        if(status.getDeliveryState() != StorageDeliveryState.IS_DEVERY){
                            if(!status.isSaved()){
                                status.setDate(awaitingDeliveryDate);
                                gf().updateStorageDeliveryStatus(status);
                            }
                        }
                    }
                }
        }
        //
        // 3. Устанавливаем статус "на выдаче", если он еще не установлен и указана ненулевая дата
        //
        if (order.getOrderStateIsAwaitingDelivery() && awaitingDeliveryDate != null) {
            gf().setOrderState_Unsafe(order.getId(), OrderState.DELIVERY);
        }
    }

    /**
     * Информация о недостающей позиции
     */
    public static class SomeItem {
        /**
         * Код товара
         */
        private String code;

        /**
         * Наименование
         */
        private String name;

        /**
         * Затребованное количество
         */
        private long requested;

        /**
         * Доступное количество
         */
        private long available;

        public SomeItem(String code, String name, long requested, long available) {
            this.code = code;
            this.name = name;
            this.requested = requested;
            this.available = available;
        }

        public String getCode() {
            return code;
        }

        public String getName() {
            return name;
        }

        public long getRequested() {
            return requested;
        }

        public long getAvailable() {
            return available;
        }
    }

    /**
     * Позиции, исключенные из заказа
     */
    private List<SomeItem> someItems = null;

    /**
     * Позиции, исключенные из заказа
     */
    public List<SomeItem> getSomeItems() {
        return someItems;
    }

    /**
     * Убрать из заказа недостающие позиции
     */
    public void doRemoveSomeItems() {
        try {
            someItems = new LinkedList<SomeItem>();

            List<OrderItem> orderItems = gf().getOrderItemsByOrderId(getSessionBean().getSelectedOrderId());
            for (OrderItem orderItem : orderItems) {
                Good good = gf().getGoodById(orderItem.getGoodId());
                if (orderItem.getCount() > (good.getCount() - good.getReservedCount())) {
                    someItems.add(new SomeItem(good.getCode(), good.getName(), orderItem.getCount(), (good.getCount() - good.getReservedCount())));
                    orderItem.setCount(good.getCount() - good.getReservedCount());
                    gf().updateOrderItem(orderItem);
                }
            }

            facesInfo("Недостающие позиции были исключены из заказа");
            orderItemsCount = null;
        } catch (ValidationException ve) {
            ve.printStackTrace();
            logger.error(ve);
            facesError("Произошла внутренняя ошибка приложения. Свяжитесь с Администратором.");
        }
    }

    /**
     * Функция предназначена для фиктивного удаления заказа
     */
    public void deleteOrder() {
        long orderId = getSessionBean().getSelectedOrderId();

        //  Разделение прав пользователей ------------------------------------------------------
        //
        //  Удалить заказ может менеджер и администратор.
        //  Кладовщик удалить заказ не может. Менеджер может удалить только свой заказ.
        //

        //  Кладовщик удалить заказ не может.
        if (getSecurityBean().getIsRoleStoreman()) throw new GoodfilSecurityException();

        //  Менеджер может удалить только свой заказ.
        if (!getSecurityBean().isManagerCorrect(gf().getOrderById(orderId).getManagerId())) {
            throw new GoodfilSecurityException();
        }
        //  -------------------------------------------------------------------------------------

        logger.debug("Deleting order: " + orderId);

        try {
            gf().softRemoveOrder(orderId);
            facesInfo("Заказ удален");
        } catch (Exception e) {
            e.printStackTrace();
            facesError("Не удалось удалить заказ");
        }
    }

    /**
     * Обновить информацию о типе доставки заказа
     */
    public void updateOrderDeliveryInfo() {
        try {
            logger.debug("updateOrderDeliveryInfo()");
            logger.debug("  delivery address : " + order.getDeliveryAddress());
            logger.debug("  delivery type    : " + order.getDeliveryTypeAsString());
            logger.debug("  delivery sum     : " + order.getDeliverySum());

            gf().updateOrder(order);
        } catch (ValidationException ex) {
            logger.error(ex);
            ex.printStackTrace();
            ValidationResult validationResult = ex.getResult();
            for (ValidationError error : validationResult.getErrors()) {
                facesError(error.toHumanString());
            }
        }
    }

    public void updateOrderReservationDate() {
        try {
            gf().updateOrder(order);
            facesInfo("Срок резервирования обновлен");
        } catch (ValidationException ex) {
            logger.error(ex);
            ex.printStackTrace();
            ValidationResult validationResult = ex.getResult();
            for (ValidationError error : validationResult.getErrors()) {
                facesError(error.toHumanString());
            }
        }
    }

    public void updateOrderDeliveryInfoExt() {

        if(order.isAdditional()){
            if(order.getMainOrderId() == null || StringUtils.isBlank(order.getMainOrderId())) {
                facesError("Для дополнительного заказа необходимо заполнить поле \"Номер основного заказа\"");
                return;
            }
            else {
                order.setMainOrderId(order.getMainOrderId().replaceAll(" ", ""));
                Order mainOrder = gf().getOrderById(order.getMainOrderIdAsLong());
                if(mainOrder == null){
                    facesError("Основной заказ не существует");
                    return;
                }
            }
        }

        if(!order.isAdditional()){
            order.setMainOrderId(null);
        }
        try {
            gf().updateOrder(order);
/*            if(order.getOrderStateIsOutput() || order.getOrderStateIsOnDelivery()){
                DataExchange de = new DataExchange();
                de.setTableName("OrderBuild");
                de.setItemId(order.getId());
                gf().addDataExchange(de);
            }*/

            facesInfo("Информация о доставке сохранена");
        } catch (ValidationException ex) {
            logger.error(ex);
            ex.printStackTrace();
            ValidationResult validationResult = ex.getResult();
            for (ValidationError error : validationResult.getErrors()) {
                facesError(error.toHumanString());
            }
        }
    }

    /**
     * Возвращает true, если у текущего заказа указанный статус
     */
    private boolean getOrderStateIs(int orderState) {
        return getOrder().getOrderStateId() == orderState;
    }

    /**
     * Текущий заказ предварительно сохранен
     */
    public boolean getOrderStateIsPreSaved() {
        return getOrderStateIs(OrderState.PRE_SAVED);
    }

    /**
     * Текущий заказ оформлен
     */
    public boolean getOrderStateIsSaved() {
        return getOrderStateIs(OrderState.SAVED);
    }

    /**
     * Текущий заказ ожидает согласования
     */
    public boolean getOrderStateIsAwaitingValidation() {
        return getOrderStateIs(OrderState.AWAITING_VALIDATION);
    }

    /**
     * Текущий заказ согласован
     */
    public boolean getOrderStateIsValidated() {
        return getOrderStateIs(OrderState.VALIDATED);
    }

    /**
     * Текущий заказ не согласован
     */
    public boolean getOrderStateIsNotValidated() {
        return getOrderStateIs(OrderState.NOT_VALIDATED);
    }

    /**
     * Текущий заказ принят бухгалтерией
     */
    public boolean getOrderStateIsAcceptedByBuh() {
        return getOrderStateIs(OrderState.ACCEPTED_BY_BUH);
    }

    /**
     * Текущий заказ выдан на доставку
     */
    public boolean getOrderStateIsOnDelivery() {
        return getOrderStateIs(OrderState.DELIVERY);
    }

    /**
     * Обработка текущего заказа завершена
     */
    public boolean getOrderStateIsComplete() {
        return getOrderStateIs(OrderState.COMPLETE);
    }

    public boolean getOrderStateIsOutput() {
        return getOrderStateIs(OrderState.OUTPUT);
    }

    /**
     * Текущий заказ допускается редактировать. Текущий заказ допускается редактировать, если он находится в одном из
     * статусов: - Предварительно сохранен - Оформлен - Не согласован - Ожидает согласования
     */
    public boolean getOrderIsEditable() {
        return getOrderStateIsPreSaved() ||
                getOrderStateIsSaved() ||
                getOrderStateIsNotValidated() ||
                getOrderStateIsAwaitingValidation();
    }

    public boolean getOrderIsEditableNew(){
        return order.getOrderIsEditableWithReserves();
    }

    /**
     * Информацию о доставке можно редактировать всегда, кроме случая, когда обработка заказа завершена
     * @return
     */
    public boolean getOrderDeliveryInfoIsEditable(){
        return !order.getOrderStateIsComplete();
    }

    /**
     * Фирма и способ оплаты - это важные подпункты из доп.информации,
     * поэтому их редактирование я выношу в отдельный метод.
     * Редактирование фирмы и типа оплаты возможно до момента проведения заказа в 1С,
     * т.е. если заказ не находится в статусах "На складе: отгрузка" или "Обработка завершена"
     * @return
     */
    public boolean getFirmAndPayTypeIsEditable(){
        return !(order.getOrderStateIsOnDelivery() || order.getOrderStateIsComplete());
    }

    /**
     * Созвращает true, если состав заказа можно изменять. А состав заказа можно изменять только пока заказ находится в
     * статуах:
     * <p/>
     * OrderState.PRE_SAVED, OrderState.SAVED, OrderState.AWAITING_VALIDATION, OrderState.NOT_VALIDATED
     */
    public boolean getOrderStateIsBeforeSaved() {
        return getOrderStateIsPreSaved() ||
                getOrderStateIsSaved() ||
                getOrderStateIsAwaitingValidation() ||
                getOrderStateIsNotValidated();
    }

    /**
     * Установить значение цены, установленной вручную для данной позиции товара
     *
     * @param manualPrice значение цены, установленной вручную для данной позиции товара
     */
    public void setManualPrice(float manualPrice) {
        this.manualPrice = manualPrice;
    }

    /**
     * Вернуть значение цены, установленной вручную для данной позиции товара
     */
    public float getManualPrice() {
        return manualPrice;
    }

    public long getOrderItemId() {
        return orderItemId;
    }

    public void setOrderItemId(long orderItemId) {
        this.orderItemId = orderItemId;
    }

    public List<Map.Entry<Brand, ClientGroup>> getCurrentClientClientgroupsInfo() {
        Order order = getOrder();
        if (order == null) return null;

        Client client = order.getClient();
        if (client == null) return null;

        Map<Long, Long> map = client.getClientgroups();
        if (map == null) return null;

        Map<Brand, ClientGroup> result = new LinkedHashMap<Brand, ClientGroup>();
        for (Long brandId : map.keySet()) {
            Long clientgroupId = map.get(brandId);
            if (clientgroupId == null) continue;

            Brand brand = gf().getBrandById(brandId);
            ClientGroup clientGroup = gf().getClientGroupById(clientgroupId);

            if (brand == null || clientGroup == null) continue;
            result.put(brand, clientGroup);
        }
        return new LinkedList<Map.Entry<Brand, ClientGroup>>(result.entrySet());
    }

    public void doOrderExport(){
        RestsExportBean exportBean=(RestsExportBean) FacesUtils.getManagedBean("RestsExportBean");
        _assertNotNull(exportBean);
        long clientId=getOrder().getClientId();
        long orderId=getOrder().getId();
        exportBean.doOrderExport(clientId,orderId);
        facesInfo("Выгружено");
    }

 //   private Order currentOrder = getOrder();


    /**
     * Объединение заказов
     */
    public void combineOrders(){
        try {
            gf().combineOrders(order.getId(),orderForCombineId);
            gf().removeOrder(orderForCombineId);
            logger.info("Заказ " + orderForCombineId + " удален из БД");
        } catch (RemoveFailureException e) {
            logger.error("Не удалось удалить заказ " + orderForCombineId + "/n" + e.getMessage()); // Удаление заказа, выбранного для объединения из БД
        } catch (ValidationException e) {
            logger.error(e.getMessage());
            facesError("Произошла ошибка " + e.getMessage());
        }
        if(getOrdersForCombine().size()==0) haveOrdersForCombine = false; // Если больше нет заказов, достпуных для объединения, то значение флага устанавливается в false
        {}
    }

    public void actualPriceForOrder(){
        List<OrderItem> orderItems = gf().getOrderItemsByOrderId(getSessionBean().getSelectedOrderId());
        for(OrderItem item : orderItems){
            item.setGood(gf().getGoodById(item.getGoodId()));
            PriceInfo pi = gf().getPrice2(item.getGoodId(),
                    item.getGood().getBrandId(),
                    order.getClientId(),
                    getSessionBean().getSelectedClientGroupId2(item.getGood().getBrandId()));
            item.setStartPrice(pi.getPrice());
            item.setPersonalPrice(pi.isPersonalPrice());
        }
        try {
            gf().updateOrderItems(orderItems);
            facesInfo("Установлены цены по актуальному прайсу");
            logger.info("Для заказа " + order.getId() + "цены пересчитаны с учетом оптимального прайса");
        } catch (ValidationException e) {
            facesError("Произошла ошибка при пересчете цен. Пожалуйста, попробуйте позже или свяжитесь с администратором");
            logger.error("Не удалось пересчитать цены для заказа " + order.getId());
        }
    }

    /**
     * Получение списка заказов для объединения.
     * Доступными для объединения считаются заказы, этого же менеджера, этого же клиента и в статусе "Предварительно сохранен"
     * @return
     */
    public List<Order> getOrdersForCombine(){
        OrdersFilter filter = new OrdersFilter();
        filter.setClientId(order.getClientId());
        filter.setManagerId(order.getManagerId());
        if(!gf().getIsReservedOn()){
            filter.setOrderStateId(OrderState.PRE_SAVED);
        }
        else if(gf().getIsReservedOn() && (getOrderStateIsAwaitingValidation() || getOrderStateIsOutput())){
            filter.setOrderStateId(order.getOrderStateId());
        }
        else return new ArrayList<Order>();
        List<Order> orders = gf().getOrders(filter);
        for(int i=0; i<orders.size();++i){
            Order currentOrdet = orders.get(i);
            if (currentOrdet.getId() == order.getId()){
                orders.remove(i);
            }
        }
        if (orders.size() != 0) haveOrdersForCombine = true;
        return orders;
    }

    /**
     * Отправка задания на обмен для получения счета на заказ
     */
    public void orderCalculate(){

        if(!isCanGetBild()){
            facesError("Невозможно получить счет на заказ");
            return;
        }

        if(order.getFirm() == 6 || order.getFirm() == 8){
            gf().getOrderCalculation(order.getId());

            facesInfo(String.format("Запрос на получение счета на заказ %d принят в %s. В течение нескольких минут счет будет отправлен на электронную почту %s",
                    order.getId(), DateFormat.FormatDate(Calendar.getInstance().getTime()), gf().getUserById(order.getManagerId()).getEmail()));
        }
        else {
            Calculation calculation = new Calculation();
            calculation.makeCalculation(getOrderItemList(), gf().getOrderById(getSessionBean().getSelectedOrderId()));
            try {
                sendClientNotification(gf().getOrderById(getSessionBean().getSelectedOrderId()).getManagerId(), gf().getOrderById(getSessionBean().getSelectedOrderId()).getId());
            } catch (MailerException e) {
                logger.error("Mailer Exception: " + e.getMessage());
            }
        }

    }

    /**
     * Проверка элементов заказа на null или на отсутствие элементов
     * @param orderItems
     * @return
     */
    private boolean checkOrderItems(List<OrderItem> orderItems){
        return orderItems != null && orderItems.size() > 0;
    }

    /**
     * Возможно ли получить счет
     * @return
     */
    public boolean isCanGetBild(){
        Order order = gf().getOrderById(getSessionBean().getSelectedOrderId());
        List<OrderItem> orderItemList = getOrderItemList();
        return checkOrderItems(orderItemList) && (order.getFirm() != -1);
    }

    /**
     * Возможно ли объединить заказы
     * @return
     */
    public boolean isCanCombineOrders(){
        List<OrderItem> orderItemList = getOrderItemList();
        boolean orderStateIsCorrect;
        if(gf().getIsReservedOn()){
            orderStateIsCorrect = getOrderStateIsAwaitingValidation() || getOrderStateIsOutput();
        }
        else {
            orderStateIsCorrect = getOrderStateIsPreSaved();
        }
        return haveOrdersForCombine && orderStateIsCorrect && checkOrderItems(orderItemList);
    }

    /**
     * Заблокировн ли клиент
     * @return
     */
    public boolean isClientBlockRender(){
        Client client = gf().getClientById(order.getClientId());
        return client.getClientIsBlock() && order.getOrderStateIsAwaitingValidation();
    }

    /**
     * Можно ли обновлять цены. Для рендеринга кнопки
     * @return
     */
    public boolean isCanUpdatePrices(){
        List<OrderItem> orderItemList = getOrderItemList();
        return checkOrderItems(orderItemList) && !getOrderStateIsComplete() && !getOrderStateIsOnDelivery();
    }

    public boolean isCanSendToStore(){
        Client client = gf().getClientById(order.getClientId());
        List<OrderItem> orderItemList = getOrderItemList();
        return checkOrderItems(orderItemList) && !client.getClientIsBlock() && (order.getOrderStateIsAwaitingValidation() || order.getOrderStateIsNotValidated() || order.getOrderStateIsSaved()) ;
    }

    /**
     * Класс для формирования сборочной накладной
     */
        class Calculation
        {
            public void makeCalculation(List<OrderItem> list, Order order)
            {
                HSSFWorkbook hssfWorkbook = new HSSFWorkbook();
                HSSFSheet sheet = hssfWorkbook.createSheet("0");
                HSSFCellStyle style = hssfWorkbook.createCellStyle();
                HSSFFont font = hssfWorkbook.createFont();
                font.setBoldweight(Font.BOLDWEIGHT_BOLD);
                style.setFont(font);
                int i = 0;
                HSSFRow header = sheet.createRow(i);
                header.createCell(0).setCellValue("Заказ клиента " + gf().getClientById(order.getClientId()).getCompanyName());
                sheet.autoSizeColumn(0);
                header.createCell(1).setCellValue("Менеджер " + getSessionBean().getClientManagerFio());
                sheet.autoSizeColumn(1);
                i++;
                HSSFRow tableHeader = sheet.createRow(i);
                Cell code =  tableHeader.createCell(0);
                code.setCellStyle(style);
                code.setCellValue("Код товара");

                Cell count = tableHeader.createCell(1);
                count.setCellStyle(style);
                count.setCellValue("Количество");

                Cell price = tableHeader.createCell(2);
                price.setCellStyle(style);
                price.setCellValue("Цена");

                Cell totalSum = tableHeader.createCell(3);
                totalSum.setCellStyle(style);
                totalSum.setCellValue("Стоимость");
                i++;
                for(OrderItem item : list){
                    HSSFRow row = sheet.createRow(i);
                    row.createCell(0).setCellValue(item.getGood().getCode());
                    row.createCell(1).setCellValue(item.getCount());
                    row.createCell(2).setCellValue(item.getItemPrice());
                    row.createCell(3).setCellValue(item.getSum());
                    i++;
                }
                HSSFRow total = sheet.createRow(i);
                Cell totalCell = total.createCell(2);
                totalCell.setCellStyle(style);
                totalCell.setCellValue("Итого: ");
                total.createCell(3).setCellValue(getSessionBean().getTotal() + " р.");
                try {
                    FileOutputStream file = new FileOutputStream("C:\\is\\temp\\заказ № " + order.getId() +".xls");
                    // Запись
                    hssfWorkbook.write(file);
                    // Закрытие потока
                    file.close();
                    facesInfo("Сборочная накладная сформирована");
                } catch (FileNotFoundException e) {
                    logger.error("File not found: " + e.getMessage());
                    e.printStackTrace();
                } catch (IOException e) {
                    logger.error("I/O Exception: " + e.getMessage());
                    e.printStackTrace();
                }

            }

        }

}
